"""
C++ AST visitor using libclang for extracting code elements.

Provides structured traversal of C++ AST nodes to extract functions, classes,
templates, and other language constructs using the visitor pattern.
"""

from typing import Dict, List, Any, Optional, Set, Callable
from dataclasses import dataclass, field
import logging

try:
    from clang.cindex import Cursor, CursorKind, TypeKind, AccessSpecifier
    LIBCLANG_AVAILABLE = True
except ImportError:
    LIBCLANG_AVAILABLE = False
    # Create dummy classes for type hints when libclang is not available
    class Cursor: pass
    class CursorKind: pass
    class TypeKind: pass
    class AccessSpecifier: pass

from ...core.error_handler import AnalysisError, log_debug


@dataclass
class VisitorContext:
    """Context maintained during AST traversal."""
    current_namespace: List[str] = field(default_factory=list)
    current_class: Optional[str] = None
    current_function: Optional[str] = None
    template_context: List[Dict[str, Any]] = field(default_factory=list)
    scope_stack: List[Dict[str, Any]] = field(default_factory=list)
    access_level: str = "public"


class CppASTVisitor:
    """
    AST visitor for extracting C++ code elements using libclang.
    
    This visitor traverses the C++ AST and extracts structured information
    about functions, classes, templates, and other language constructs.
    """
    
    def __init__(self):
        """Initialize the C++ AST visitor."""
        if not LIBCLANG_AVAILABLE:
            raise AnalysisError("libclang not available for AST visiting")
        
        self.reset()
    
    def reset(self):
        """Reset visitor state for processing a new file."""
        self.functions = []
        self.classes = []
        self.templates = []
        self.namespaces = []
        self.includes = []
        self.variables = []
        self.typedefs = []
        self.enums = []
        self.function_calls = []
        self.method_calls = []
        self.constructor_calls = []
        self.template_instantiations = []
        self.inheritance_relations = []
        self.using_declarations = []
        
        self.context = VisitorContext()
        self._handlers = self._setup_handlers()
    
    def _setup_handlers(self) -> Dict[CursorKind, Callable]:
        """Setup cursor kind handlers."""
        return {
            CursorKind.FUNCTION_DECL: self._handle_function,
            CursorKind.CXX_METHOD: self._handle_method,
            CursorKind.CONSTRUCTOR: self._handle_constructor,
            CursorKind.DESTRUCTOR: self._handle_destructor,
            CursorKind.CLASS_DECL: self._handle_class,
            CursorKind.STRUCT_DECL: self._handle_struct,
            CursorKind.UNION_DECL: self._handle_union,
            CursorKind.NAMESPACE: self._handle_namespace,
            CursorKind.FUNCTION_TEMPLATE: self._handle_function_template,
            CursorKind.CLASS_TEMPLATE: self._handle_class_template,
            CursorKind.TEMPLATE_TYPE_PARAMETER: self._handle_template_parameter,
            CursorKind.TEMPLATE_NON_TYPE_PARAMETER: self._handle_template_parameter,
            CursorKind.TEMPLATE_TEMPLATE_PARAMETER: self._handle_template_parameter,
            CursorKind.VAR_DECL: self._handle_variable,
            CursorKind.FIELD_DECL: self._handle_field,
            CursorKind.TYPEDEF_DECL: self._handle_typedef,
            CursorKind.TYPE_ALIAS_DECL: self._handle_type_alias,
            CursorKind.ENUM_DECL: self._handle_enum,
            CursorKind.ENUM_CONSTANT_DECL: self._handle_enum_constant,
            CursorKind.CALL_EXPR: self._handle_call_expression,
            CursorKind.CXX_MEMBER_CALL_EXPR: self._handle_member_call,
            CursorKind.CXX_CONSTRUCT_EXPR: self._handle_constructor_call,
            CursorKind.DECL_REF_EXPR: self._handle_declaration_reference,
            CursorKind.MEMBER_REF_EXPR: self._handle_member_reference,
            CursorKind.INCLUSION_DIRECTIVE: self._handle_include,
            CursorKind.USING_DECLARATION: self._handle_using_declaration,
            CursorKind.USING_DIRECTIVE: self._handle_using_directive,
            CursorKind.CXX_ACCESS_SPEC_DECL: self._handle_access_specifier,
            CursorKind.CXX_BASE_SPECIFIER: self._handle_base_class
        }
    
    def visit_translation_unit(self, translation_unit, clang_wrapper) -> Dict[str, Any]:
        """Visit a translation unit and extract all information."""
        self.reset()
        self.clang_wrapper = clang_wrapper
        
        # Start traversal from root cursor
        self._visit_cursor(translation_unit.cursor)
        
        return {
            'functions': self.functions,
            'classes': self.classes,
            'templates': self.templates,
            'namespaces': self.namespaces,
            'includes': self.includes,
            'variables': self.variables,
            'typedefs': self.typedefs,
            'enums': self.enums,
            'function_calls': self.function_calls,
            'method_calls': self.method_calls,
            'constructor_calls': self.constructor_calls,
            'template_instantiations': self.template_instantiations,
            'inheritance_relations': self.inheritance_relations,
            'using_declarations': self.using_declarations
        }
    
    def _visit_cursor(self, cursor: Cursor, depth: int = 0):
        """Recursively visit a cursor and its children."""
        if not cursor or depth > 100:  # Prevent infinite recursion
            return
        
        try:
            # Handle this cursor
            handler = self._handlers.get(cursor.kind)
            if handler:
                handler(cursor)
            
            # Visit children
            for child in cursor.get_children():
                self._visit_cursor(child, depth + 1)
                
        except Exception as e:
            log_debug(f"Error visiting cursor {cursor.kind}: {e}")
    
    def _get_cursor_location(self, cursor: Cursor) -> Dict[str, Any]:
        """Get location information from cursor."""
        try:
            location = cursor.location
            extent = cursor.extent
            return {
                'file': location.file.name if location.file else None,
                'line': location.line,
                'column': location.column,
                'start_line': extent.start.line if extent else location.line,
                'end_line': extent.end.line if extent else location.line,
                'start_column': extent.start.column if extent else location.column,
                'end_column': extent.end.column if extent else location.column
            }
        except:
            return {'file': None, 'line': 0, 'column': 0}
    
    def _get_qualified_name(self, name: str) -> str:
        """Get fully qualified name including namespace."""
        if self.context.current_namespace:
            namespace_path = "::".join(self.context.current_namespace)
            return f"{namespace_path}::{name}"
        return name
    
    def _handle_function(self, cursor: Cursor):
        """Handle function declaration."""
        cursor_info = self.clang_wrapper.get_cursor_info(cursor)
        location = self._get_cursor_location(cursor)
        
        function_info = {
            'name': cursor.spelling,
            'qualified_name': self._get_qualified_name(cursor.spelling),
            'location': location,
            'return_type': cursor_info.get('type', {}).get('result_type', {}),
            'parameters': self._extract_parameters(cursor),
            'is_template': bool(self._get_template_info(cursor)),
            'template_info': self._get_template_info(cursor),
            'is_inline': 'inline' in cursor_info.get('storage_class', '').lower(),
            'is_static': cursor_info.get('storage_class') == 'static',
            'is_extern': cursor_info.get('linkage') == 'external',
            'is_definition': cursor.is_definition(),
            'namespace': list(self.context.current_namespace),
            'access_level': self.context.access_level,
            'usr': cursor_info.get('usr', ''),
            'attributes': self._extract_attributes(cursor)
        }
        
        self.functions.append(function_info)
    
    def _handle_method(self, cursor: Cursor):
        """Handle method declaration."""
        cursor_info = self.clang_wrapper.get_cursor_info(cursor)
        location = self._get_cursor_location(cursor)
        
        method_info = {
            'name': cursor.spelling,
            'qualified_name': self._get_qualified_name(cursor.spelling),
            'location': location,
            'return_type': cursor_info.get('type', {}).get('result_type', {}),
            'parameters': self._extract_parameters(cursor),
            'is_virtual': cursor.is_virtual_method(),
            'is_pure_virtual': cursor.is_pure_virtual_method(),
            'is_static': cursor.is_static_method(),
            'is_const': self._is_const_method(cursor),
            'is_override': self._has_override_attribute(cursor),
            'is_final': self._has_final_attribute(cursor),
            'access_level': self.context.access_level,
            'class_name': self.context.current_class,
            'is_template': bool(self._get_template_info(cursor)),
            'template_info': self._get_template_info(cursor),
            'is_definition': cursor.is_definition(),
            'usr': cursor_info.get('usr', ''),
            'attributes': self._extract_attributes(cursor)
        }
        
        self.functions.append(method_info)
    
    def _handle_constructor(self, cursor: Cursor):
        """Handle constructor declaration."""
        self._handle_method(cursor)  # Constructors are special methods
    
    def _handle_destructor(self, cursor: Cursor):
        """Handle destructor declaration."""
        self._handle_method(cursor)  # Destructors are special methods
    
    def _handle_class(self, cursor: Cursor):
        """Handle class declaration."""
        old_class = self.context.current_class
        old_access = self.context.access_level
        
        self.context.current_class = cursor.spelling
        self.context.access_level = "private"  # Default for class
        
        cursor_info = self.clang_wrapper.get_cursor_info(cursor)
        location = self._get_cursor_location(cursor)
        
        class_info = {
            'name': cursor.spelling,
            'qualified_name': self._get_qualified_name(cursor.spelling),
            'location': location,
            'kind': 'class',
            'is_template': bool(self._get_template_info(cursor)),
            'template_info': self._get_template_info(cursor),
            'base_classes': [],
            'methods': [],
            'fields': [],
            'nested_classes': [],
            'access_level': old_access,
            'namespace': list(self.context.current_namespace),
            'is_abstract': self._is_abstract_class(cursor),
            'is_final': self._has_final_attribute(cursor),
            'usr': cursor_info.get('usr', ''),
            'attributes': self._extract_attributes(cursor)
        }
        
        # Extract base classes
        for child in cursor.get_children():
            if child.kind == CursorKind.CXX_BASE_SPECIFIER:
                base_info = self._extract_base_class_info(child)
                class_info['base_classes'].append(base_info)
                
                self.inheritance_relations.append({
                    'derived_class': self._get_qualified_name(cursor.spelling),
                    'base_class': base_info['name'],
                    'access_level': base_info['access_level'],
                    'is_virtual': base_info['is_virtual'],
                    'location': location
                })
        
        self.classes.append(class_info)
        
        # Visit children to collect methods and fields
        for child in cursor.get_children():
            self._visit_cursor(child)
        
        # Restore context
        self.context.current_class = old_class
        self.context.access_level = old_access
    
    def _handle_struct(self, cursor: Cursor):
        """Handle struct declaration (similar to class but public by default)."""
        old_access = self.context.access_level
        self.context.access_level = "public"  # Default for struct
        self._handle_class(cursor)
        self.context.access_level = old_access
    
    def _handle_union(self, cursor: Cursor):
        """Handle union declaration."""
        self._handle_class(cursor)  # Unions are handled similarly to classes
    
    def _handle_namespace(self, cursor: Cursor):
        """Handle namespace declaration."""
        old_namespace = list(self.context.current_namespace)
        
        if cursor.spelling:  # Named namespace
            self.context.current_namespace.append(cursor.spelling)
        
        location = self._get_cursor_location(cursor)
        
        namespace_info = {
            'name': cursor.spelling or '<anonymous>',
            'qualified_name': "::".join(self.context.current_namespace),
            'location': location,
            'parent_namespace': "::".join(old_namespace) if old_namespace else None,
            'is_anonymous': not bool(cursor.spelling)
        }
        
        self.namespaces.append(namespace_info)
        
        # Visit children
        for child in cursor.get_children():
            self._visit_cursor(child)
        
        # Restore namespace context
        self.context.current_namespace = old_namespace
    
    def _handle_function_template(self, cursor: Cursor):
        """Handle function template declaration."""
        # Function templates are handled like regular functions but with template info
        self._handle_function(cursor)
    
    def _handle_class_template(self, cursor: Cursor):
        """Handle class template declaration."""
        # Class templates are handled like regular classes but with template info
        self._handle_class(cursor)
    
    def _handle_template_parameter(self, cursor: Cursor):
        """Handle template parameter."""
        # Template parameters are collected in template_info
        pass
    
    def _handle_variable(self, cursor: Cursor):
        """Handle variable declaration."""
        cursor_info = self.clang_wrapper.get_cursor_info(cursor)
        location = self._get_cursor_location(cursor)
        
        variable_info = {
            'name': cursor.spelling,
            'qualified_name': self._get_qualified_name(cursor.spelling),
            'location': location,
            'type': cursor_info.get('type', {}),
            'is_global': not self.context.current_function and not self.context.current_class,
            'is_static': 'static' in cursor_info.get('storage_class', '').lower(),
            'is_extern': cursor_info.get('linkage') == 'external',
            'is_const': cursor_info.get('type', {}).get('is_const_qualified', False),
            'namespace': list(self.context.current_namespace),
            'class_name': self.context.current_class,
            'function_name': self.context.current_function,
            'usr': cursor_info.get('usr', '')
        }
        
        self.variables.append(variable_info)
    
    def _handle_field(self, cursor: Cursor):
        """Handle class field declaration."""
        # Fields are similar to variables but belong to classes
        self._handle_variable(cursor)
    
    def _handle_typedef(self, cursor: Cursor):
        """Handle typedef declaration."""
        cursor_info = self.clang_wrapper.get_cursor_info(cursor)
        location = self._get_cursor_location(cursor)
        
        typedef_info = {
            'name': cursor.spelling,
            'qualified_name': self._get_qualified_name(cursor.spelling),
            'location': location,
            'underlying_type': cursor_info.get('type', {}),
            'namespace': list(self.context.current_namespace),
            'usr': cursor_info.get('usr', '')
        }
        
        self.typedefs.append(typedef_info)
    
    def _handle_type_alias(self, cursor: Cursor):
        """Handle type alias declaration (using = type)."""
        # Type aliases are handled similarly to typedefs
        self._handle_typedef(cursor)
    
    def _handle_enum(self, cursor: Cursor):
        """Handle enum declaration."""
        cursor_info = self.clang_wrapper.get_cursor_info(cursor)
        location = self._get_cursor_location(cursor)
        
        enum_info = {
            'name': cursor.spelling or '<anonymous>',
            'qualified_name': self._get_qualified_name(cursor.spelling) if cursor.spelling else '<anonymous>',
            'location': location,
            'is_scoped': self._is_scoped_enum(cursor),
            'underlying_type': self._get_enum_underlying_type(cursor),
            'values': [],
            'namespace': list(self.context.current_namespace),
            'class_name': self.context.current_class,
            'usr': cursor_info.get('usr', '')
        }
        
        # Extract enum values
        for child in cursor.get_children():
            if child.kind == CursorKind.ENUM_CONSTANT_DECL:
                enum_info['values'].append({
                    'name': child.spelling,
                    'value': getattr(child, 'enum_value', None),
                    'location': self._get_cursor_location(child)
                })
        
        self.enums.append(enum_info)
    
    def _handle_enum_constant(self, cursor: Cursor):
        """Handle enum constant declaration."""
        # Enum constants are handled by their parent enum
        pass
    
    def _handle_call_expression(self, cursor: Cursor):
        """Handle function call expression."""
        location = self._get_cursor_location(cursor)
        
        # Try to get the called function
        called_function = None
        for child in cursor.get_children():
            if child.kind == CursorKind.DECL_REF_EXPR:
                called_function = child.spelling
                break
        
        if called_function:
            call_info = {
                'function_name': called_function,
                'location': location,
                'caller_function': self.context.current_function,
                'caller_class': self.context.current_class,
                'arguments': self._extract_call_arguments(cursor),
                'is_template_instantiation': self._is_template_call(cursor)
            }
            
            self.function_calls.append(call_info)
    
    def _handle_member_call(self, cursor: Cursor):
        """Handle member function call expression."""
        location = self._get_cursor_location(cursor)
        
        call_info = {
            'method_name': cursor.spelling,
            'location': location,
            'caller_function': self.context.current_function,
            'caller_class': self.context.current_class,
            'object_type': self._get_object_type(cursor),
            'arguments': self._extract_call_arguments(cursor)
        }
        
        self.method_calls.append(call_info)
    
    def _handle_constructor_call(self, cursor: Cursor):
        """Handle constructor call expression."""
        location = self._get_cursor_location(cursor)
        
        constructor_info = {
            'class_name': self._get_constructed_type(cursor),
            'location': location,
            'caller_function': self.context.current_function,
            'caller_class': self.context.current_class,
            'arguments': self._extract_call_arguments(cursor)
        }
        
        self.constructor_calls.append(constructor_info)
    
    def _handle_declaration_reference(self, cursor: Cursor):
        """Handle declaration reference expression."""
        # This captures variable and function references
        pass
    
    def _handle_member_reference(self, cursor: Cursor):
        """Handle member reference expression."""
        # This captures member accesses
        pass
    
    def _handle_include(self, cursor: Cursor):
        """Handle include directive."""
        location = self._get_cursor_location(cursor)
        
        include_info = {
            'file': cursor.spelling,
            'location': location,
            'is_system_include': self._is_system_include(cursor),
            'resolved_path': self._resolve_include_path(cursor)
        }
        
        self.includes.append(include_info)
    
    def _handle_using_declaration(self, cursor: Cursor):
        """Handle using declaration."""
        location = self._get_cursor_location(cursor)
        
        using_info = {
            'name': cursor.spelling,
            'location': location,
            'namespace': list(self.context.current_namespace),
            'type': 'declaration'
        }
        
        self.using_declarations.append(using_info)
    
    def _handle_using_directive(self, cursor: Cursor):
        """Handle using directive."""
        location = self._get_cursor_location(cursor)
        
        using_info = {
            'namespace': cursor.spelling,
            'location': location,
            'current_namespace': list(self.context.current_namespace),
            'type': 'directive'
        }
        
        self.using_declarations.append(using_info)
    
    def _handle_access_specifier(self, cursor: Cursor):
        """Handle access specifier (private, protected, public)."""
        if cursor.access_specifier == AccessSpecifier.PUBLIC:
            self.context.access_level = "public"
        elif cursor.access_specifier == AccessSpecifier.PROTECTED:
            self.context.access_level = "protected"
        elif cursor.access_specifier == AccessSpecifier.PRIVATE:
            self.context.access_level = "private"
    
    def _handle_base_class(self, cursor: Cursor):
        """Handle base class specifier."""
        # Base classes are handled by the class handler
        pass
    
    # Helper methods for extracting detailed information
    
    def _extract_parameters(self, cursor: Cursor) -> List[Dict[str, Any]]:
        """Extract function parameters."""
        parameters = []
        for child in cursor.get_children():
            if child.kind == CursorKind.PARM_DECL:
                param_info = self.clang_wrapper.get_cursor_info(child)
                parameters.append({
                    'name': child.spelling or f'param_{len(parameters)}',
                    'type': param_info.get('type', {}),
                    'default_value': self._get_default_value(child),
                    'location': self._get_cursor_location(child)
                })
        return parameters
    
    def _get_template_info(self, cursor: Cursor) -> Dict[str, Any]:
        """Extract template information from cursor."""
        template_info = {}
        
        try:
            # Get template parameters
            template_params = []
            for child in cursor.get_children():
                if child.kind in (CursorKind.TEMPLATE_TYPE_PARAMETER,
                                  CursorKind.TEMPLATE_NON_TYPE_PARAMETER,
                                  CursorKind.TEMPLATE_TEMPLATE_PARAMETER):
                    param_info = {
                        'name': child.spelling,
                        'kind': child.kind.name,
                        'location': self._get_cursor_location(child)
                    }
                    template_params.append(param_info)
            
            if template_params:
                template_info['parameters'] = template_params
                
        except Exception as e:
            log_debug(f"Error extracting template info: {e}")
        
        return template_info
    
    def _extract_attributes(self, cursor: Cursor) -> List[str]:
        """Extract attributes from cursor."""
        attributes = []
        try:
            for child in cursor.get_children():
                if 'ATTR' in child.kind.name:
                    attributes.append(child.kind.name)
        except:
            pass
        return attributes
    
    def _is_const_method(self, cursor: Cursor) -> bool:
        """Check if method is const."""
        try:
            return cursor.type.is_const_qualified() if cursor.type else False
        except:
            return False
    
    def _has_override_attribute(self, cursor: Cursor) -> bool:
        """Check if method has override attribute."""
        return 'override' in self._extract_attributes(cursor)
    
    def _has_final_attribute(self, cursor: Cursor) -> bool:
        """Check if has final attribute."""
        return 'final' in self._extract_attributes(cursor)
    
    def _is_abstract_class(self, cursor: Cursor) -> bool:
        """Check if class is abstract (has pure virtual methods)."""
        for child in cursor.get_children():
            if child.kind == CursorKind.CXX_METHOD and child.is_pure_virtual_method():
                return True
        return False
    
    def _extract_base_class_info(self, cursor: Cursor) -> Dict[str, Any]:
        """Extract base class information."""
        return {
            'name': cursor.type.spelling if cursor.type else cursor.spelling,
            'access_level': cursor.access_specifier.name.lower() if hasattr(cursor, 'access_specifier') else 'private',
            'is_virtual': cursor.is_virtual_base() if hasattr(cursor, 'is_virtual_base') else False,
            'location': self._get_cursor_location(cursor)
        }
    
    def _get_default_value(self, cursor: Cursor) -> Optional[str]:
        """Get default parameter value."""
        # This is challenging with libclang - may need source text analysis
        return None
    
    def _extract_call_arguments(self, cursor: Cursor) -> List[Dict[str, Any]]:
        """Extract call arguments."""
        arguments = []
        for child in cursor.get_children():
            if child.kind not in (CursorKind.DECL_REF_EXPR, CursorKind.MEMBER_REF_EXPR):
                continue
            arguments.append({
                'expression': child.spelling,
                'type': self.clang_wrapper.get_type_info(child.type) if child.type else {},
                'location': self._get_cursor_location(child)
            })
        return arguments
    
    def _is_template_call(self, cursor: Cursor) -> bool:
        """Check if this is a template function call."""
        # Look for template arguments in the call
        for child in cursor.get_children():
            if 'TEMPLATE' in child.kind.name:
                return True
        return False
    
    def _get_object_type(self, cursor: Cursor) -> Dict[str, Any]:
        """Get the type of object being called."""
        return self.clang_wrapper.get_type_info(cursor.type) if cursor.type else {}
    
    def _get_constructed_type(self, cursor: Cursor) -> str:
        """Get the type being constructed."""
        return cursor.type.spelling if cursor.type else 'unknown'
    
    def _is_system_include(self, cursor: Cursor) -> bool:
        """Check if include is a system include."""
        # System includes typically use angle brackets
        return cursor.spelling.startswith('<') and cursor.spelling.endswith('>')
    
    def _resolve_include_path(self, cursor: Cursor) -> Optional[str]:
        """Resolve include path to actual file."""
        # This would require integration with include path resolution
        return None
    
    def _is_scoped_enum(self, cursor: Cursor) -> bool:
        """Check if enum is scoped (enum class)."""
        try:
            return cursor.enum_type.is_scoped_enum() if hasattr(cursor, 'enum_type') else False
        except:
            return False
    
    def _get_enum_underlying_type(self, cursor: Cursor) -> Dict[str, Any]:
        """Get underlying type of enum."""
        try:
            underlying_type = cursor.enum_type
            return self.clang_wrapper.get_type_info(underlying_type) if underlying_type else {}
        except:
            return {}